package com.lakeworks.common.utils.tool;


import com.lakeworks.common.utils.DateUtils;
import lombok.Getter;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.json.JsonObject;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.function.BiFunction;
import java.util.function.Function;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.ZoneOffset.UTC;
import static java.time.format.DateTimeFormatter.BASIC_ISO_DATE;
import static javax.json.Json.createArrayBuilder;
import static javax.json.Json.createObjectBuilder;
import static org.springframework.util.Base64Utils.encodeToString;

@Component
public class S3Tool {

    @Value("${lakeworks.app}")
    private String code;

    private static final String ALGORITHM = "HmacSHA256";
    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    //private static final DateTimeFormatter DTF_YYMM = DateTimeFormatter.ofPattern("yyMM");

    //private static final DateTimeFormatter DTF_ISO8601 = DateTimeFormatter.ofPattern("yyyyMMdd'T000000Z'");
    @Getter
    private final URI viewUri;

    @Getter
    private final URI postUri;

    private final String bucket;

    private final String prefix;

    private final Function<LocalDate, String> getCredential;

    private final BiFunction<String, LocalDate, String> sign;


    public S3Tool(
            @Value("${aws.s3.access.key.id}") String accessKeyId,
            @Value("${aws.s3.secret.access.key}") String secretAccessKey,
            @Value("${aws.s3.region}") String region,
            @Value("${aws.s3.bucket}") String bucket,
            @Value("${aws.s3.prefix}") String prefix,
            @Value("${aws.s3.viewUri}") String viewUri
    ) {
        this.viewUri = URI.create(viewUri);
        this.postUri = URI.create(format("https://%s.s3.%s.amazonaws.com.cn/", bucket, region));
        this.bucket = bucket;
        this.prefix = prefix;
        this.sign = (policy, now) -> {
            var dateKey = hmac(format("AWS4%s", secretAccessKey).getBytes(), now.format(BASIC_ISO_DATE));
            var regionKey = hmac(dateKey, region);
            var serviceKey = hmac(regionKey, "s3");
            var signingKey = hmac(serviceKey, "aws4_request");
            var signed = hmac(signingKey, policy);
            return new String(Hex.encode(signed));
        };
        this.getCredential = now -> {
            return format("%s/%s/%s/s3/aws4_request", accessKeyId, now.format(BASIC_ISO_DATE), region);
        };
    }

    public JsonObject getUploadCredential() {
        ZonedDateTime now = ZonedDateTime.now(UTC);

        String key = this.getKey(now.toLocalDate());
        String policy = this.createPostPolicy(key, now.toInstant());
        String signature = this.sign.apply(policy, now.toLocalDate());

        var builder = createObjectBuilder();
        builder.add("key", key);
        builder.add("policy", policy.toString());
        builder.add("x-amz-algorithm", "AWS4-HMAC-SHA256");
        builder.add("x-amz-credential", getCredential.apply(now.toLocalDate()));
        builder.add("x-amz-date", now.toLocalDate().format(BASIC_ISO_DATE) + "T000000Z");
        builder.add("x-amz-signature", signature);
        builder.add("viewUri", viewUri.toString());
        builder.add("postUri", postUri.toString());
        return builder.build();
    }

    private String getKey(
            LocalDate now
    ) {
        return format("%s/%s/%s/", prefix,DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD),code);
    }

    private String createPostPolicy(
            String key,
            Instant now
    ) {
        var conditions = createArrayBuilder();
        conditions.add(createObjectBuilder().add("bucket", bucket));
        //conditions.add(createObjectBuilder().add("key", key));
        conditions.add(createArrayBuilder().add("starts-with").add("$key").add(key));
        conditions.add(createObjectBuilder().add("x-amz-algorithm", "AWS4-HMAC-SHA256"));
        conditions.add(createObjectBuilder().add("x-amz-credential", getCredential.apply(LocalDate.ofInstant(now, UTC))));
        conditions.add(createObjectBuilder().add("x-amz-date", LocalDate.ofInstant(now, UTC).format(BASIC_ISO_DATE) + "T000000Z"));
        conditions.add(createArrayBuilder().add("starts-with").add("$Content-Type").add("image/"));
        //conditions.add(createArrayBuilder().add("starts-with").add("$uploadField").add(""));

        var builder = createObjectBuilder();
        builder.add("expiration", now.plusSeconds(600).toString());
        builder.add("conditions", conditions);

        var policy = builder.build().toString();

        log.info("Created policy: {}", policy);
        return encodeToString(policy.getBytes(UTF_8));
    }

    @SneakyThrows({InvalidKeyException.class, NoSuchAlgorithmException.class})
    private byte[] hmac(
            byte[] keyBytes,
            String content
    ) {
        var key = new SecretKeySpec(keyBytes, ALGORITHM);
        var mac = Mac.getInstance(ALGORITHM);
        mac.init(key);
        return mac.doFinal(content.getBytes(UTF_8));
    }

    //public static void main(
    //		String[] args
    //) {
    //	var dateKey = hmac(format("AWS4%s", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY").getBytes(), "20150830");
    //	var regionKey = hmac(dateKey, "us-east-1");
    //	var serviceKey = hmac(regionKey, "iam");
    //	var signingKey = hmac(serviceKey, "aws4_request");

    //	System.out.println(Hex.encode(signingKey));
    //}


}

